/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smfield.c - interactive work on fields */

#include <time.h>
#include <setjmp.h>
#include <unistd.h>
    
#define SM_CLASS extern
#include "smlib.h"
#include "smio.h"
#ifdef SM_XVIEW
#include "smxview.h"
#endif
#ifdef SM_XAW
#include "smxaw.h"
#endif

CHAR *smfield_rcsid(void) {
    return("$Id: smfield.c,v 1.8 2008/02/20 13:20:32 bhepple Exp $");
}

SM_FIELD	sm_last_field = NULL;
static CHAR	*field_yank = NULL;
	
void sm_paint_fld ARG5(int,		window,
		       CHAR *, 		start, 
		       SM_FIELD, 	field, 
		       int, 		enhance,
		       int,		do_base)
{
    assert(field);

    if (!(field->hidden))
#ifdef SM_XVIEW
    if (smio_using_xview) {
	if (do_base)
	    field->xv_ptr = smxview_create_fld(field->row, 
					       field->col, 
					       abs(field->len));
	smxview_set_fld(field->xv_ptr, field->c_value);
    } else
#endif
	/* Display exactly field->len characters */
	{
	    CHAR	*buf;
	    int		len = abs(field->len);

	    buf = alloca(len + 1);
	    
	    memset(buf, ' ', len);
	    if (start) {
		int 	slen  = strlen(start);
		if (slen > len)
		    slen = len;
		memcpy(buf, start, slen);
	    }
	    buf[len] = 0;
#ifdef SM_XAW
	    if (smio_using_xaw)
		smxaw_set_fld(field, buf);
	    else
#endif
		{
		    smio_goto(window, field->row, field->col);
		    if (enhance) {
			smio_enh(window, field->attribute);
			smio_puts(window, buf);
			smio_enh(window, 0);
		    } else
			smio_puts(window, buf);
		}
	}
}

static int reject_char ARG1(CHAR *, msg)
{
    CHAR	*buf = alloca(100);

    sprintf(buf, "Illegal character for this field (type=%s)", msg);
    sm_message(buf);
    sm_bell();
    return(0);
}

struct enter_fld_s {
    f_ptr	destructor;
    SM_FIELD	field;
    SM_SCREEN	target_for_popups;
    CHAR	*start_display;	/* First bit of c_value which is displayed*/
    CHAR	*pos;		/* Where the cursor is in c_vlaue */
    CHAR	*maxpos;	/* Maximum value for pos */
    int		write_protect;
    int		window;
    INDEX	index;
};

CALLBACK(do_field_popup)
{
    struct enter_fld_s *enter_fld = (struct enter_fld_s *)callback_data;

    /* Always called after sm_selection */
    if (command == SM_YES_C) {
	/* Insert the returned value in the field */
	CHAR *p, *end, *value = smdb_seek(sm_prompt_scr, enter_fld->index);
	int	ipos, istart;

	if (enter_fld->field->disallow_insert) {
	    enter_fld->pos = enter_fld->field->c_value;
	    *enter_fld->field->c_value = 0;
	}

	/* 'enter_fld->pos' may be lost in the realloc */
	ipos   = enter_fld->pos - 
	    enter_fld->field->c_value;
	istart = enter_fld->start_display - 
	    enter_fld->field->c_value;
	if (value && (*value == '$'))
	    value++;

	enter_fld->field->c_value = 
	    xrealloc(enter_fld->field->c_value, 
		     (INDEX) (strlen(enter_fld->field->c_value) + strlen(value) + 1));
	enter_fld->pos = enter_fld->field->c_value + ipos;
	enter_fld->start_display = enter_fld->field->c_value + istart;
	
	end = strchr(enter_fld->field->c_value, 0) + strlen(value);
	p   = strchr(enter_fld->field->c_value, 0);
	while (p >= enter_fld->pos) 
	    *end-- = *p--;

	while (*value)
	    *(enter_fld->pos)++ = *value++;
	enter_fld->field->changed = 1;
	command = SM_POPCALLBACK;
    } else if (command == SM_QUIT_C)
	command = SM_POPCALLBACK;

    smdb_delete_data(sm_prompt_scr);
    return(command);
}

/* Need to keep track of the screen row & column, what part of
   the string is being displayed, and where in the field is the 
   cursor. */
CALLBACK(sm_prep_fld)
{
    struct enter_fld_s *enter_fld = (struct enter_fld_s *)callback_data;
    
    if (enter_fld->field != NULL) {
	
	enter_fld->maxpos = 
	    enter_fld->start_display + 
	    smio_maxcol(enter_fld->window, 
			enter_fld->field->col + abs(enter_fld->field->len)) - 
	    enter_fld->field->col;
	if (enter_fld->field->len < 0)
	    enter_fld->maxpos--;

	if ((enter_fld->field->len > 0) && 
	    (enter_fld->pos > enter_fld->maxpos))
	    enter_fld->start_display += (enter_fld->pos - enter_fld->maxpos);
	
	if (enter_fld->pos < enter_fld->start_display)
	    enter_fld->start_display = enter_fld->pos;
	
	sm_paint_fld(enter_fld->window, 
		     enter_fld->start_display, 
		     enter_fld->field, 
		     /* enhance = */ 1,
		     /* do_base = */ 0);
	
	if ((enter_fld->field->len < 0) && 
	    (enter_fld->pos > enter_fld->maxpos))
	    return(SM_NEXT_FIELD_C);

#if SM_XAW
	if (!smio_using_curses) {
	  /* smxaw_realise_form(); */
	    smxaw_set_cursor(enter_fld->field, 
			     enter_fld->pos - enter_fld->start_display);
	} else
#endif
	smio_goto(enter_fld->window,
		  enter_fld->field->row, 
		  enter_fld->field->col + 
		  enter_fld->pos - 
		  enter_fld->start_display);
    }
    if (command == SM_NOT_A_CALLBACK)
	return(0);
    else
	return(command);
}

CALLBACK(do_popup)
{
    struct enter_fld_s *enter_fld = (struct enter_fld_s *)callback_data;

    /* Always called after sm_selection */
    if (command == SM_YES_C) {
	/* Insert the returned value in the field */
	CHAR *p, *end, *value = smdb_seek(sm_prompt_scr, enter_fld->index);
	int	ipos, istart;

	if (enter_fld->field->disallow_insert) {
	    enter_fld->pos = enter_fld->field->c_value;
	    *enter_fld->field->c_value = 0;
	}

	/* 'enter_fld->pos' may be lost in the realloc */
	ipos   = enter_fld->pos - 
	    enter_fld->field->c_value;
	istart = enter_fld->start_display - 
	    enter_fld->field->c_value;

	enter_fld->field->c_value = 
	    xrealloc(enter_fld->field->c_value, 
		     (INDEX) (strlen(enter_fld->field->c_value) + strlen(value) + 1));
	enter_fld->pos = enter_fld->field->c_value + ipos;
	enter_fld->start_display = enter_fld->field->c_value + istart;
	
	end = strchr(enter_fld->field->c_value, 0) + strlen(value);
	p   = strchr(enter_fld->field->c_value, 0);
	while (p >= enter_fld->pos) 
	    *end-- = *p--;

	while (*value)
	    *(enter_fld->pos)++ = *value++;
	enter_fld->field->changed = 1;
	command = SM_POPCALLBACK;
    } else if (command == SM_QUIT_C)
	command = SM_POPCALLBACK;

    smdb_delete_data(sm_prompt_scr);
    return(command);
}

static int setup_popup ARG1(struct enter_fld_s *, enter_fld)
{
    assert(enter_fld && enter_fld->field);

    smdb_delete_data(sm_prompt_scr);
    if (enter_fld->field->pop_up) {
	STRING	*p;
	
	for (p = (STRING *)firstArrayItemp(enter_fld->field->pop_up); 
	     p && *p; 
	     p = (STRING *)nextArrayItemp(enter_fld->field->pop_up, (void **)p))
	    smdb_write(sm_prompt_scr, StringToStr(*p));
    }
    smdb_seek(sm_prompt_scr, (INDEX) 0);
    
    enter_fld->index = 0;
    sm_add_callback(do_popup, NULL);
    sm_setup_selection(sm_prompt_scr,
		       &enter_fld->index,
		       enter_fld->field->row,
		       enter_fld->field->col,
		       NULL,
		       NULL,
		       NULL);
    return(0);
}

static int setup_field_popup ARG1(struct enter_fld_s *, enter_fld)
{
    SM_SCREEN	screen = enter_fld->target_for_popups;
    SM_FIELD	*fieldp;
    CHAR	buf[80];
    
    if (screen) {
	smdb_delete_data(sm_prompt_scr);
	/* Build list for this screen */
	if (screen->data_field) {
	    CHAR	**p;

	    for (p = (CHAR **) firstArrayItemp(screen->data_field); 
		 p && *p; 
		 p = (CHAR **) nextArrayItemp(screen->data_field, (void **) p))
		smdb_write(sm_prompt_scr, *p);
	} else
	    for (fieldp = sm_first_fld(screen);
		 fieldp && *fieldp;
		 fieldp = sm_next_fld(fieldp)) {
		strcpy(buf, "$");
		strcat(buf, (*fieldp)->field_name);
		smdb_write(sm_prompt_scr, buf);
	    }
	smdb_seek(sm_prompt_scr, (INDEX) 0);
	
	enter_fld->index = 0;
	sm_add_callback(do_field_popup, NULL);
	sm_setup_selection(sm_prompt_scr,
			   &enter_fld->index,
			   enter_fld->field->row,
			   enter_fld->field->col,
			   NULL,
			   NULL,
			   NULL);
	return(0);
    } else
	return(1);
}

CALLBACK(do_file_popup)
{
    struct enter_fld_s *enter_fld = (struct enter_fld_s *)callback_data;
    
    /* Always called after sm_selection */
    if (command == SM_YES_C) {
	CHAR	*filename = alloca(MAXPATHLEN);
	CHAR	*direct   = alloca(MAXPATHLEN);
	
	/* Insert the returned value in the field */
	sm_getfilename(filename, direct);
	chdir(direct);
	sm_fld_set(enter_fld->field, filename);
	enter_fld->start_display = enter_fld->pos = enter_fld->field->c_value;
	command = SM_POPCALLBACK;
    } else if (command == SM_QUIT_C)
	command = SM_POPCALLBACK;
    
    return(command);
}

static int setup_file_popup ARG3(struct enter_fld_s *, enter_fld, 
				 int, dir_only,
				 int, disallow_chdir)
{
    CHAR	*mask = alloca(100), *msg;

    strcpy(mask, enter_fld->field->c_default);

    /* supply default to avoid SEGV - but what is that business below
     * about 'already on popup list? */
    if (!*mask)
        strcpy(mask, "*");
    
    if ((msg = strchr(mask, ' ')) != NULL)
	*msg++ = 0;
    
    sm_add_callback(do_file_popup, NULL);
    if (*mask)
	return(sm_setup_getfilename(mask, NULL, msg, dir_only, disallow_chdir));
    else { /* List of files is already on popup list */
	sm_message(msg);
	return(setup_popup(enter_fld));
    }
}

void sm_add_field_popup_item ARG2(SM_FIELD, field, CHAR *, item)
{
    assert(field);

    if (item && *item)
	field->pop_up = addToArray(field->pop_up, StrToString(NULL, item));
}

void sm_delete_field_popups ARG1(SM_FIELD, field)
{
    assert(field);
    
    if (field->pop_up) {
	removeAllItemsFromArray(field->pop_up, (f_ptr) freeStringF);
	freeArray(field->pop_up);
    }
}

int enter_fld_destructor ARG1(struct enter_fld_s *, object)
{
    free(object);
    return(0);
}

f_ptr _sm_get_field_trigger ARG1(SM_FIELD, 	field)
{
    f_ptr	retval = sm_edit_get_field_trigger(field);

    if (retval)
	return(retval);
    else
	return(sm_get_field_trigger(field)); /* call users function */
}

/* Hit on every event for booleans and on field exits for others */
static int hit_trigger ARG3(int, window, int, command, SM_FIELD, field)
{
    int		retval = command;

    assert(field);

    if (field->trigger_fn &&
	*(field->trigger_fn)) {
	f_ptr	func;

	func = _sm_get_field_trigger(field);
	if (func == NULL) {
	    CHAR	buf[120];
	    sprintf(buf, "No such field trigger function: <%s>", 
		    field->trigger_fn);
	    sm_message(buf);
	} else
	    retval = (*func)(field, retval, window);
    }
    return(retval);
}

static COMMAND process_bool_field ARG3(int,		window,
				       SM_FIELD, 	field,
				       COMMAND, 	command)
{
    CHAR	buf[2];
    CHAR	*value;
    COMMAND	new_val = command;

    assert(field);

    value = sm_fld_enq(field);
    buf[1] = 0;
    if ((command == 'Y') || (command == 'N'))
	buf[0] = command;
    else if ((*value == 'Y') || (*value == '1'))
	buf[0] = 'N';
    else
	buf[0] = 'Y';
    if (buf[0] != *value)
	sm_fld_set(field, buf);
    new_val = hit_trigger(window, command, field);
    if (new_val != command)
	return(new_val);
    else
	return(0);
} 

/* Any special processing relevant for a mouse hit should be done here */
void sm_mouse_hit_on_fld ARG2(SM_FIELD, field, int, window)
{
    if (field && field->ftype == SM_BOOL_FT)
	process_bool_field(window, field, ' ');
}

/* Move left one word in the string 'start' */
CHAR *sm_left_word ARG2(CHAR *, start, CHAR *, pos)
{
    if ((*start == 0) || (pos == start))
	return(start);
    pos--;
    /* Move left till first non space */
    while ((pos > start) && isspace(*pos))
	pos--;
    /* Move left till space */
    if (pos > start) {
	while ((pos > start) && 
	       !isspace(*pos))
	    pos--;
	if (isspace(*pos))
	    pos++;
    }
    return(pos);
}

/* Move right one word (space delimited) in the string 'start' */
CHAR *sm_right_word ARG2(CHAR *, start, CHAR *, pos)
{
    if (*pos == 0)
	return(pos);
    /* Move right till first non-space */
    while (*pos && isspace(*pos))
	pos++;
    /* Move right till first space */
    while (*pos && !isspace(*pos))
	pos++;
    return(pos);
}

int sm_transpose_words ARG2(CHAR *, str, CHAR *, pos)
{
    CHAR *w1 = 0, *gap = 0, *w2 = 0, *p, *end;
    
    /* Find nearest gap between words */
    if (isspace(*pos))
	gap = pos;
    else {
	/* Look to the left of current position */
	if (pos > str) {
	    if (isspace(*(pos - 1)))
		w1 = pos - 1;
	    else {
		w1 = sm_left_word(str, pos);
		if (w1 > str)
		    w1--;
	    }
	    /* See if w1 is a leading gap */
	    if (isspace(*w1)) {
		p = w1;
		while ((p > str) && isspace(*p))
		    p--;
		if (isspace(*p))
		    w1 = 0; /* No good to us */
	    } else
		w1 = 0;
	}
	
	/* Now, look to the right of the current position */
	w2 = pos;
	while (*w2 && !isspace(*w2))
	    w2++;
	/* See if w2 is a trailing space */
	if (*w2) {
	    p = w2;
	    while (*p && isspace(*p))
		p++;
	    if (!*p)
		w2 = 0;
	} else
	    w2 = 0;
	
	if (w1 && w2) /* We have two candidates */
	    if (pos - w1 < w2 - pos)
		gap = w1;
	    else
		gap = w2;
	else
	    gap = w1?w1:w2;
    }
    
    if (!gap)
	return(SM_ERROR);
    
    /* Find the first gap character */
    while ((gap >= str) && isspace(*gap))
	gap--;
    if (gap < str)
	return(SM_ERROR);
    gap++;
    
    /* At this point we definitely have a gap between words */
    w1 = sm_left_word(str, gap);
    end = sm_right_word(str, gap) - 1;
    if (isspace(*(end - 1)))
	w2 = end;
    else
	w2 = sm_left_word(str, end);
    
    {
	CHAR *d, *t = alloca(SM_MAXREC);
	/* Copy gap out */
	p = gap;
	d = t;
	while (p != w2)
	    *d++ = *p++;
	*d++ = 0;
	/* Copy 1st word out */
	p = w1;
	while (p != gap)
	    *d++ = *p++;
	*d++ = 0;
	/* Copy 2nd word to 1st position */
	p = w2;
	d = w1;
	while (p <= end)
	    *d++ = *p++;
	/* Copy gap back */
	p = t;
	while (*p)
	    *d++ = *p++;
	/* Copy w1 back */
	p++;
	while (*p)
	    *d++ = *p++;
	
	pos = end + 1;
    }

    return(0);
}

static void input_string_in_field ARG2(struct enter_fld_s *, 	enter_fld, 
				       CHAR *, 			input)
{
    if (enter_fld && enter_fld->field && input && *input &&
        !(enter_fld->write_protect)) {
	
	int ftype = enter_fld->field->ftype;

	if (ftype == SM_BOOL_FT)
	    process_bool_field(enter_fld->window,
			       enter_fld->field, 
			       *input);
	else {
	    CHAR	*buf = alloca(strlen(enter_fld->field->c_value) +
				      strlen(input) + 1);
	    int		left_len = enter_fld->pos - 
		                   enter_fld->field->c_value;
	    int		istart =   enter_fld->start_display - 
		                   enter_fld->field->c_value;
	    CHAR	*right_buf;

	    strncpy(buf, enter_fld->field->c_value, left_len);
	    buf[left_len] = 0;
	    strcat(buf, input);
	    right_buf = enter_fld->pos;
	    if (!smio_insertp()) {
		int	i;
		for (i = strlen(input); *right_buf && i; i--, right_buf++)
		    ;
	    }
	    strcat(buf, right_buf);
	    SM_FASSIGN(enter_fld->field->c_value, 
		       buf,
		       (enter_fld->field->len < 0)?
		       abs(enter_fld->field->len): 0);
	    enter_fld->pos = enter_fld->field->c_value + 
		left_len +
		strlen(input);
	    enter_fld->start_display = enter_fld->field->c_value + istart;
	}
	enter_fld->field->changed = 1;
	
	if (ftype != SM_BOOL_FT) {
	    int maxlen = (enter_fld->field->len < 0) ?
		abs(enter_fld->field->len) : enter_fld->field->max_len;

	    if (maxlen &&
		(strlen(enter_fld->field->c_value) > maxlen)) {
		enter_fld->field->c_value[maxlen] = 0;
		if (enter_fld->pos >= enter_fld->field->c_value + maxlen)
		    enter_fld->pos =  
			enter_fld->field->c_value + maxlen - 1;
	    }
	}
    }
}

CALLBACK(sm_enter_fld)
{
    struct enter_fld_s 	*enter_fld = (struct enter_fld_s *) callback_data;
    int			retval = 0, ftype;

    if (enter_fld->field == NULL)
	return(command);
    
    /**************/
    /* USER INPUT */
    /**************/
    
    if (command <= 0)
	return(0); /*error-probably an undefined function key*/
    
    /**********************/
    /* CHARACTER CHECKING */
    /**********************/
    
    ftype = enter_fld->field->ftype;

    if (((enter_fld->field->parent->parent->separator == ' ') && 
	 ((command == ' ') || (command == '\t'))) || 
	 (command == enter_fld->field->parent->parent->separator)) {
	sm_error("It's not a good idea to use the separator character in the data!");
	command = 0;
    } else if ((command >= ' ') && (command <= '~')) {
	
	switch (ftype) {
	    
	case SM_ALPHANUM_FT:
	    break;
	    
	case SM_INTEGER_FT:
	    if (((command < '0') || (command > '9')) &&
		(command != '-') &&
		(command != '+'))
		command = reject_char("integer");
	    break;
	    
	case SM_FLOAT_FT:
	case SM_FIXED_FT:
	    if (((command < '0') || (command > '9')) &&
		(command != '.') &&
		(toupper(command) != 'E') &&
		(command != '-') &&
		(command != '+'))
		command = reject_char("floating point");
	    break;
	    
	case SM_BINARY_FT:
	    if ((command != '0') && (command != '1'))
		command = reject_char("binary");
	    break;
	    
	case SM_OCTAL_FT:
	    if ((command < '0') || (command > '7'))
		command = reject_char("octal");
	    break;
	    
	case SM_HEX_FT:
	    command = toupper(command);
	    if (((command >= '0') && (command <= '9')) ||
		((command >= 'A') && (command <= 'F')))
		break;
	    command = reject_char("hexadecimal");
	    break;
	    
	case SM_LETTERS_FT:
	    if (((command >= 'a') && (command <= 'z')) ||
		((command >= 'A') && (command <= 'Z')))
		break;
	    command = reject_char("letter");
	    break;
	    
	case SM_BOOL_FT:
	    command = toupper(command);
	    enter_fld->pos = enter_fld->field->c_value;
	    if (command == '0')
		command = 'N';
	    if (command == '1')
		command = 'Y';
	    if ((command != 'N') && 
		(command != 'Y') && 
		(command != '0') &&
		(command != '1') &&
		(command != ' '))
		command = reject_char("boolean");
	    break;
	    
	case SM_IDATE_FT:
	case SM_ADATE_FT:
	case SM_TIME_FT:
	    if ((command < '0') || (command > '9'))
		command = reject_char("date/time");
	    
	default:
	    break;
	}
    }
    
    if ((command > 0) &&
	(command < 0xff)) { /* ie not a command */
	
	    CHAR	input[2];
	    input[0] = command;
	    input[1] = 0;
	    input_string_in_field(enter_fld, input);

#if 0
	if (!(enter_fld->write_protect)) {
	    if (ftype == SM_BOOL_FT)
		process_bool_field(enter_fld->window,
				   enter_fld->field, 
				   command);
	    else if (smio_insertp() || 
		     (enter_fld->pos == 
		      strchr(enter_fld->field->c_value, 0))) {
		if ((enter_fld->field->len > 0) || 
		    (enter_fld->pos <= enter_fld->maxpos)) {
		    /* Insert a character */
		    
		    /* 'enter_fld->pos' may be lost in the realloc */
		    CHAR *end;
		    int  ipos = enter_fld->pos - enter_fld->field->c_value;
		    int  istart = enter_fld->start_display - 
			enter_fld->field->c_value;
		    enter_fld->field->c_value = 
			xrealloc(enter_fld->field->c_value, 
				 (INDEX) (strlen(enter_fld->field->c_value) + 2));
		    enter_fld->pos = enter_fld->field->c_value + ipos;
		    enter_fld->start_display = enter_fld->field->c_value + istart;
		    
		    end = strchr(enter_fld->field->c_value, 0) + 1;
		    /* 1 past the end! */
		    while (end > enter_fld->pos) {
			*end = *(end - 1);
			end--;
		    }
		    
		    *(enter_fld->pos)++ = command;
		}
	    } else {
		/* Overwrite a character */
		*(enter_fld->pos)++ = command;
	    }
	    enter_fld->field->changed = 1;
	    
	    if ((ftype != SM_BOOL_FT) &&
		(enter_fld->field->len > 0) && 
		enter_fld->field->max_len && 
		(strlen(enter_fld->field->c_value) > enter_fld->field->max_len)) {
		enter_fld->field->c_value[enter_fld->field->max_len] = 0;
		if (enter_fld->pos >= enter_fld->field->c_value + 
		                      enter_fld->field->max_len)
		    enter_fld->pos =  enter_fld->field->c_value + 
			              enter_fld->field->max_len - 1;
	    }
	}
#endif 

    } else { /* Process commands */
	switch (command) {
	    
	case SM_LEFT_C:
	    if (enter_fld->pos > enter_fld->field->c_value)
		enter_fld->pos--;
	    else
		retval = SM_PREV_FIELD_C;
	    break;
	    
	case SM_RIGHT_C:
	    if (enter_fld->field->len > 0) {
		if (enter_fld->pos < strchr(enter_fld->field->c_value, 0))
		    enter_fld->pos++;
	    } else {
		if (enter_fld->pos < enter_fld->maxpos)
		    enter_fld->pos++;
		else
		    retval = SM_NEXT_FIELD_C;
	    }
	    break;
	    
	case SM_MOUSE_EVENT_C:
	    {
		int	mouse_button, mouse_row, mouse_col, mouse_delta;

		mouse_button = sm_enq_mouse(&mouse_row, 
					    &mouse_col, 
					    &mouse_delta);
		if ((mouse_row != enter_fld->field->row) ||
		    (mouse_col < enter_fld->field->col) ||
		    (mouse_col > enter_fld->field->col + 
		     abs(enter_fld->field->len))) {
		    /* Pick is not in current field */
		    retval = command;
		    break;
		}
		if (ftype == SM_BOOL_FT) {
		    if (mouse_button & SM_MOUSE_UP)
			retval = process_bool_field(enter_fld->window,
						    enter_fld->field, ' ');
		    else
			retval = 0;
		} else {
		    enter_fld->pos = enter_fld->start_display + 
			mouse_col - enter_fld->field->col;
		    if (enter_fld->pos > 
			strchr(enter_fld->field->c_value, 0)) {
			enter_fld->pos = 
			    strchr(enter_fld->field->c_value, 0);
			if (enter_fld->field->len < 0)
			    enter_fld->pos--;
		    }			    
		    if (enter_fld->pos > enter_fld->maxpos)
			enter_fld->pos = enter_fld->maxpos;
		}
	    }
	    break;
	    
	case SM_DEL_CHAR_C:
	    if (!(enter_fld->write_protect)) {
		CHAR *end = enter_fld->pos;
		while (*end) {
		    *end = *(end + 1);
		    end++;
		}
		enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_DEL_BACK_C:
	    if (!(enter_fld->write_protect)) {
		if (enter_fld->pos > enter_fld->field->c_value) {
		    CHAR *end = --enter_fld->pos;
		    while (*end) {
			*end = *(end + 1);
			end++;
		    }
		}
		enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_HOME_FIELD_C:
	    enter_fld->pos = enter_fld->field->c_value;
	    break;
	    
	case SM_END_FIELD_C:
	    enter_fld->pos = strchr(enter_fld->field->c_value, 0);
	    if (enter_fld->field->len < 0)
		enter_fld->pos--;
	    break;
	    
	case SM_CLEAR_FIELD_C:
	    if (!(enter_fld->write_protect)) {
		SM_ASSIGN(field_yank, enter_fld->pos);
		*enter_fld->pos = 0;
		enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_COPY_FIELD_C:
	    SM_ASSIGN(field_yank, enter_fld->field->c_value);
	    break;
	    
	case SM_DELETE_FIELD_C:
	    if (!(enter_fld->write_protect)) {
		*(enter_fld->field->c_value) = 0;
		enter_fld->pos = enter_fld->field->c_value;
		enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_YANK_FIELD_C:
	    if (!(enter_fld->write_protect)) {
		input_string_in_field(enter_fld, field_yank);
	    }
	    break;
	    
	case SM_CLEAR_SCREEN_C:
	    if (!(enter_fld->write_protect)) {
		*enter_fld->pos = 0;
		enter_fld->field->changed = 1;
		retval = command;
	    } else
		break;
	    
	case SM_TRANSPOSE_CHARS_C:
	    if (!(enter_fld->write_protect)) {
		if (*enter_fld->pos && 
		    (enter_fld->pos > enter_fld->field->c_value)) {
		    CHAR temp = *enter_fld->pos;
		    *enter_fld->pos = *(enter_fld->pos - 1);
		    *(enter_fld->pos - 1) = temp;
		    enter_fld->pos++;
		    enter_fld->field->changed = 1;
		}
	    }
	    break;
	    
	case SM_TOGGLE_CASE_C:
	    if (!(enter_fld->write_protect)) {
		CHAR *end = enter_fld->pos;
		while (*end) {
		    if (isupper(*end))
			*end = tolower(*end);
		    else if (islower(*end))
			*end = toupper(*end);
		    end++;
		}
		enter_fld->pos++;
		enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_LEFT_WORD_C:
	    enter_fld->pos = sm_left_word(enter_fld->field->c_value, 
					  enter_fld->pos);
	    break;
	    
	case SM_RIGHT_WORD_C:
	    enter_fld->pos = sm_right_word(enter_fld->field->c_value, 
					   enter_fld->pos);
	    break;
	    
	case SM_DEL_WORD_C:
	    if (!(enter_fld->write_protect)) {
		CHAR *end = sm_right_word(enter_fld->field->c_value, 
					  enter_fld->pos);
		if (end != enter_fld->pos) {
		    CHAR *p = enter_fld->pos;
		    while ((*p++ = *end++))
			;
		    enter_fld->field->changed = 1;
		}
	    }
	    break;
	    
	case SM_DEL_WORD_BACK_C:
	    if (!(enter_fld->write_protect)) {
		if (enter_fld->pos > enter_fld->field->c_value) {
		    CHAR *end = sm_left_word(enter_fld->field->c_value, 
					     enter_fld->pos);
		    CHAR *p = end;
		    while ((*end++ = *enter_fld->pos++))
			;
		    enter_fld->pos = p;
		    enter_fld->field->changed = 1;
		}
	    }
	    break;
	    
	case SM_TRANSPOSE_WORDS_C:
	    if (!(enter_fld->write_protect)) {
		if (sm_transpose_words(enter_fld->field->c_value, 
				       enter_fld->pos) == SM_ERROR)
		    sm_message("Can't transpose words");
		else
		    enter_fld->field->changed = 1;
	    }
	    break;
	    
	case SM_POPUP_C:
	    if (!(enter_fld->write_protect)) {
		/* Let field->pop_up list take precedence */
		if (enter_fld->field->pop_up) {
		    sm_add_callback(sm_prep_fld, NULL);
		    if (setup_popup(enter_fld)) {
			sm_destroy_callback();
			retval = 0;
		    } else
			return(0);
		} else if (ftype == SM_FIELD_FT) {
		    sm_add_callback(sm_prep_fld, NULL);
		    if (setup_field_popup(enter_fld)) {
			sm_destroy_callback();
			retval = 0;
		    } else
			return(0);
		} else if ((ftype == SM_FILE_FT) || 
			   (ftype == SM_DIR_FT)) {
		    sm_add_callback(sm_prep_fld, NULL);
		    if (setup_file_popup(enter_fld, 
					 (ftype == SM_DIR_FT),
					 enter_fld->field->disallow_chdir)) {
			sm_destroy_callback();
			retval = 0;
		    } else
			return(0);
		} else /* Let sm_edit handle a master field */
		    retval = command;
	    } else
		retval = command;
	    break;

	case SM_POPCALLBACK: /* from a popup menu */
	    retval = 0;
	    break;

	default:
	    retval = command;
	    break;
	}
    }
    
    if ((ftype != SM_BOOL_FT) && retval)
	retval = hit_trigger(enter_fld->window,
			     retval,
			     enter_fld->field);

    if (retval == 0) /* then we are to stay in the field */
	retval = sm_prep_fld(SM_NOT_A_CALLBACK, enter_fld);
    return(retval);
}

/* Allow user to fill in a field */ 
int sm_setup_fld ARG5(int, 		window, 
		      SM_FIELD, 	field, 
		      int, 		write_protect,
		      SM_SCREEN, 	target_for_popups,
		      int,		target_col)
{	
    struct enter_fld_s	*enter_fld;
    int			i;

    sm_last_field = field;

    enter_fld = (struct enter_fld_s *)xmalloc((INDEX) sizeof(struct enter_fld_s));
    enter_fld->destructor = enter_fld_destructor;
    
    enter_fld->field = field;
    if (target_for_popups)
	enter_fld->target_for_popups = target_for_popups;
    else
	enter_fld->target_for_popups = field? field->parent: NULL;

    if (enter_fld->field != NULL) {
	enter_fld->field->changed = 0;
	
	if (enter_fld->field->hidden)
	    return(SM_NEXT_FIELD_C);
	
	if (enter_fld->field->protect)
	    write_protect = 1;

	/* Make sure we've got something to point at */
	if (enter_fld->field->c_value == NULL) {
	    enter_fld->field->c_value = (CHAR *) xmalloc((INDEX) 1);
	    enter_fld->field->c_value[0] = 0;
	}
	
	enter_fld->start_display = enter_fld->field->c_value;
	
	i = target_col - enter_fld->field->col;
	if (i > 0) {
	    if (i > strlen(enter_fld->field->c_value))
		i = strlen(enter_fld->field->c_value);
	} else
	    i = 0;
	enter_fld->pos = enter_fld->field->c_value + i;

	enter_fld->write_protect = write_protect;
	enter_fld->window = window;
    }
    sm_add_callback(sm_enter_fld, enter_fld);
    return(sm_prep_fld(SM_NOT_A_CALLBACK, enter_fld));
}

/* Find out what field the cursor was last in */
SM_FIELD sm_get_field()
{
    return(sm_last_field);
}

/* Set the field for next time */
void sm_set_field ARG1(SM_FIELD, f)
{
    sm_last_field = f;
}

/* Expand field values $xxx
   Fieldname is $[a-zA-Z_][a-zA-Z0-9_]*
   A $ can also delimit a fieldname
   Use $$ for $
   */
int sm_expand_fields ARG3(SM_SCREEN, 		screen, 
			  CHAR *, 		output, 
			  CHAR *, 		input)
{
    CHAR	*s = input, *d, *end, *p, store;
    SM_FIELD	f;

    assert(screen && output && input);

    d = output;
    while (*s) {
	if (*s == '$') {
	    s++;
	    if (*s == '$') {
		*d++ = *s++;
	    } else if (isalpha(*s) || (*s == '_')) { /* We have a fieldname */
		end = s; /* Use end to find the end of the fieldname */
		while (isalnum(*end) || (*end == '_'))
		    end++;
		store = *end;
		*end = 0;
		/* expand the field ... */
		f = sm_fld_open(screen, s);
		if (f == NULL)
		    return(-1);
		p = sm_fld_enq(f);
		strcpy(d, p);
		d = strchr(d, 0);
		*end = store;
		s = end;
		if (*s == '$') /* $ terminates the fieldname */
		    s++;
	    } else /* The $ did not start a fieldname */
		*d++ = '$';
	} else
	    *d++ = *s++;
    }
    *d = 0;

    return(0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
